#ifndef DATETIMEFORMAT_HPP
#define DATETIMEFORMAT_HPP

#include <string>
#include <memory>
#include <ctime>
#include <cstring>

namespace base
{
namespace utils
{

const std::string MONTH_WORD[12] = {"January", "February", "March", "April", "May", "June",
                                    "July", "August", "September", "October", "November", "December"};

const std::string WEEK_WORD[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

/**
 * 日期格式化，根据Qt源码改写而来，和Qt的格式化表达式相同
 *
 * These expressions may be used for the date:
 * d    :   the day as number without a leading zero (1 to 31)
 * dd   :   the day as number with a leading zero (01 to 31)
 * ddd  :   the abbreviated localized day name (e.g. 'Mon' to 'Sun'). Uses the system locale to localize the name, i.e. QLocale::system().
 * dddd :   the long localized day name (e.g. 'Monday' to 'Sunday'). Uses the system locale to localize the name, i.e. QLocale::system().
 * M    :   the month as number without a leading zero (1-12)
 * MM   :   the month as number with a leading zero (01-12)
 * MMM  :   the abbreviated localized month name (e.g. 'Jan' to 'Dec'). Uses the system locale to localize the name, i.e. QLocale::system().
 * MMMM :   the long localized month name (e.g. 'January' to 'December'). Uses the system locale to localize the name, i.e. QLocale::system().
 * yy   :   the year as two digit number (00-99)
 * yyyy :   the year as four digit number
 * h    :   the hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display)
 * hh   :   the hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display)
 * H    :   the hour without a leading zero (0 to 23, even with AM/PM display)
 * HH   :   the hour with a leading zero (00 to 23, even with AM/PM display)
 * m    :   the minute without a leading zero (0 to 59)
 * mm   :   the minute with a leading zero (00 to 59)
 * s    :   the second without a leading zero (0 to 59)
 * ss   :   the second with a leading zero (00 to 59)
 * z    :   the milliseconds without leading zeroes (0 to 999)
 * zzz  :   the milliseconds with leading zeroes (000 to 999)
 * AP/A :   use AM/PM display. A/AP will be replaced by either "AM" or "PM".
 * ap/a :   use am/pm display. a/ap will be replaced by either "am" or "pm".
 *
 * @author: sherlock_lht
 */
class DateTimeFormat
{
public:
    /**
     * time_t按照指定format格式化，这里也可以直接传入时间戳，1900距今的秒数
     * @author: sherlock_lht
     */
    static std::string formatDateTime(const std::string &format, time_t time, long long int msecond)
    {
        tm tm{};
        if (!localtime_r(&time, &tm))
        {
            fprintf(stderr, "localtime_r error, err: %s", strerror(errno));
        }
        return formatDateTime(format, tm, msecond);
    }
    /**
     * tm按照指定的format格式化
     * @author: sherlock_lht
     */
    static std::string formatDateTime(const std::string &format, tm tm, long long int msecond)
    {
        std::string result;
        int i = 0;
        while (i < format.size())
        {
            if (format.at(i) == '\'')
            {
                result.append(readEscapedFormatString(format, &i));
                continue;
            }
            const char c = format.at(i);
            int repeat = repeatCount(format, i);
            bool used = false;
            switch (c)
            {
                case 'y':
                    used = true;
                    if (repeat >= 4)
                        repeat = 4;
                    else if (repeat >= 2)
                        repeat = 2;
                    switch (repeat)
                    {
                        case 4:
                            result.append(formatStr("%04d", tm.tm_year + 1900));
                            break;
                        case 2:
                            result.append(formatStr("%02d", (tm.tm_year + 1900) % 100));
                            break;
                        default:
                            repeat = 1;
                            result.push_back(c);
                            break;
                    }
                    break;
                case 'M':
                    used = true;
                    repeat = std::min(repeat, 4);
                    switch (repeat)
                    {
                        case 1:
                            result.append(formatStr("%01d", tm.tm_mon + 1));
                            break;
                        case 2:
                            result.append(formatStr("%02d", tm.tm_mon + 1));
                            break;
                        case 3:
                            result.append(getMonthWord(tm.tm_mon, 3));
                            break;
                        case 4:
                            result.append(getMonthWord(tm.tm_mon));
                            break;
                    }
                    break;
                case 'd':
                    used = true;
                    repeat = std::min(repeat, 4);
                    switch (repeat)
                    {
                        case 1:
                            result.append(formatStr("%01d", tm.tm_mday));
                            break;
                        case 2:
                            result.append(formatStr("%02d", tm.tm_mday));
                            break;
                        case 3:
                            result.append(getWeekWork(tm.tm_wday, 3));
                            break;
                        case 4:
                            result.append(getWeekWork(tm.tm_wday));
                            break;
                    }
                    break;
                case 'h':
                {
                    used = true;
                    repeat = std::min(repeat, 2);
                    int hour = tm.tm_hour;
                    if (timeFormatContainsAP(format))
                    {
                        if (hour > 12)
                            hour -= 12;
                        else if (hour == 0)
                            hour = 12;
                    }
                    switch (repeat)
                    {
                        case 1:
                            result.append(formatStr("%d", hour));
                            break;
                        case 2:
                            result.append(formatStr("%02d", hour));
                            break;
                    }
                    break;
                }
                case 'H':
                    used = true;
                    repeat = std::min(repeat, 2);
                    switch (repeat)
                    {
                        case 1:
                            result.append(formatStr("%d", tm.tm_hour));
                            break;
                        case 2:
                            result.append(formatStr("%02d", tm.tm_hour));
                            break;
                    }
                    break;
                case 'm':
                    used = true;
                    repeat = std::min(repeat, 2);
                    switch (repeat)
                    {
                        case 1:
                            result.append(formatStr("%d", tm.tm_min));
                            break;
                        case 2:
                            result.append(formatStr("%02d", tm.tm_min));
                            break;
                    }
                    break;
                case 's':
                    used = true;
                    repeat = std::min(repeat, 2);
                    switch (repeat)
                    {
                        case 1:
                            result.append(formatStr("%d", tm.tm_sec));
                            break;
                        case 2:
                            result.append(formatStr("%02d", tm.tm_sec));
                            break;
                    }
                    break;
                case 'a':
                    used = true;
                    if (i + 1 < format.length() && format.at(i + 1) == 'p')
                    {
                        repeat = 2;
                    }
                    else
                    {
                        repeat = 1;
                    }
                    result.append(tm.tm_hour < 12 ? "am" : "pm");
                    break;
                case 'A':
                    used = true;
                    if (i + 1 < format.length() && format.at(i + 1) == 'P')
                    {
                        repeat = 2;
                    }
                    else
                    {
                        repeat = 1;
                    }
                    result.append(tm.tm_hour < 12 ? "AM" : "PM");
                    break;
                case 'z':
                    used = true;
                    if (repeat >= 3)
                    {
                        repeat = 3;
                    }
                    else
                    {
                        repeat = 1;
                    }
                    switch (repeat)
                    {
                        case 1:
                            result.append(formatStr("%d", msecond));
                            break;
                        case 3:
                            result.append(formatStr("%03d", msecond));
                            break;
                    }
                    break;
                default:
                    break;

            }
            if (!used)
            {
                result.append(std::string(repeat, c));
            }
            i += repeat;
        }

        return result;
    }
private:
    static std::string getMonthWord(int tm_month, int sep_length = -1)
    {
        if (tm_month < 0 || tm_month > 11)
        {
            return std::string("");
        }
        return MONTH_WORD[tm_month].substr(0, sep_length);
    }

    static std::string getWeekWork(int tm_week, int sep_length = -1)
    {
        if (tm_week < 0 || tm_week > 6)
        {
            return std::string("");
        }
        return WEEK_WORD[tm_week].substr(0, sep_length);
    }

    static std::string readEscapedFormatString(const std::string &format, int *idx)
    {
        int &i = *idx;
        ++i;
        if (i == format.size())
            return std::string("");
        if (format.at(i) == '\'')// "''" outside of a quoted stirng
        {
            ++i;
            return std::string("'");
        }

        std::string result;
        while (i < format.size())
        {
            if (format.at(i) == '\'')
            {
                if (i + 1 < format.size() && format.at(i + 1) == '\'')
                {
                    result.push_back('\'');
                    i += 2;
                }
                else
                {
                    break;
                }
            }
            else
            {
                result.push_back(format.at(i++));
            }
        }
        if (i < format.size())
        {
            ++i;
        }
        return result;
    }

    static bool timeFormatContainsAP(const std::string &format)
    {
        int i = 0;
        while (i < format.size())
        {
            if (format.at(i) == '\'')
            {
                readEscapedFormatString(format, &i);
                continue;
            }
            if (std::tolower(format.at(i)) == 'a')
            {
                return true;
            }
            ++i;
        }
        return false;
    }

    static int repeatCount(const std::string &s, int i)
    {
        char c = s.at(i);
        int j = i + 1;
        while (j < s.size() && s.at(j) == c)
            ++j;
        return j - i;
    }

    template<typename ... Args>
    static std::string formatStr(const char *format, Args ... args)
    {
        auto size_buf = std::snprintf(nullptr, 0, format, args ...) + 1;
        std::unique_ptr<char[]> buf(new(std::nothrow) char[size_buf]);

        if (!buf)
            return std::string("");

        std::snprintf(buf.get(), size_buf, format, args ...);
        return std::string(buf.get(), buf.get() + size_buf - 1);
    }

};
}
}

#endif //DATETIMEFORMAT_HPP
